/*
通过文件句柄获取文件路径
    以通配符*.txt查找文件
    判断是否是空文件
    打开文件，获取文件句柄
    创建文件映射对象
    创建view，将view映射到调用进程的内存虚拟地址空间
    获取文件设备名"\Device\HarddiskVolume5\test\test.pdf"
    转化为文件的完整路径：
        获取驱动器设备名"\Device\HarddiskVolume5"的长度
        （不含结束反斜杠）
        依次获取每个驱动器设备名，以便后续比较
            获取所有卷的根路径"D:\"……
            转化为卷名"\\?\Volume{95bf0695-6ee2-4a6d-8075-82be10d7ef37\"
            格式化为"Volume{95bf0695-6ee2-4a6d-8075-82be10d7ef37}"
            转换为设备名"\Device\HarddiskVolume5"
            比对
        返回匹配的驱动器根路径，并组装成文件的完整路径

提示"undefined reference to `GetMappedFileNameA'"，编译命令链接加上参数-lpsapi（链接libpsapi.a）：gcc test.c -lpsapi -otestpsapi -finput-charset=UTF-8 -fexec-charset=GBK -o test.exe
SetCurrentDirectory、GetCurrentDirectory函数
FindFirstFile、FindClose函数
CreateFileMapping、MapViewOfFile、UnmapViewOfFile、CloseHandle函数
CreateFile、CloseHandle函数
GetMappedFileName、QueryDosDevice、GetVolumeNameForVolumeMountPoint函数
GetFileAttributesEx、GetLogicalDriveStrings函数
CopyMemory函数
WIN32_FIND_DATA结构、WIN32_FILE_ATTRIBUTE_DATA结构
_tcscpy、_tcsnccmp、_tcslen函数

过于繁杂，以GetLogicalDrives函数代替GetLogicalDriveStrings函数，应会更为简练
*/
#include <windows.h>
#include <psapi.h>
#include <tchar.h>

#define FILEFINDSTR "*.pdf"

void FindFile(PCTSTR szFindStr, PTSTR szFileName);
void ChkFileSize(PCTSTR szFileName);
void GetDeviceName(PCTSTR szFileName, PTSTR szDeviceName);
void ConvertFullPath(PCTSTR szDeviceName);
void GetDeviceNameLen(PCTSTR szDeviceName, INT *iDeviceNameLen);
void GetVolPath(PCTSTR szDeviceName, PTSTR szVolPathEach, INT iDeviceNameLen);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    // 搜索得到的文件名
    TCHAR szFileName[MAX_PATH] = {'\0'};
    // 设备名"\Device\HarddiskVolume5\"
    TCHAR szDeviceName[MAX_PATH] = {'\0'};

    SetCurrentDirectory("d:\\ebooks\\辞典");
    GetCurrentDirectory(MAX_PATH, szFileName);
    _tprintf(TEXT("当前目录：\t%s\n"), szFileName);

    // 查找文件，获取匹配的文件名
    FindFile(FILEFINDSTR, szFileName);
    _tprintf(TEXT("文件名：\t%s\n"), szFileName);

    // 获取文件大小，判断是否为空
    ChkFileSize(szFileName);

    // 获取搜索文件的设备名
    GetDeviceName(szFileName, szDeviceName);
    _tprintf(TEXT("文件的完整设备名：\t%s\n"), szDeviceName);

    // 将文件的设备名转化为完整的路径
    ConvertFullPath(szDeviceName);

    return 0;
}

/* 查找文件，获取匹配的文件名 */
void FindFile(PCTSTR szFindStr, PTSTR szFileName)
{
    WIN32_FIND_DATA FindFileData;
    HANDLE hFileFind = FindFirstFile(szFindStr, &FindFileData);

    if (hFileFind==INVALID_HANDLE_VALUE && GetLastError()==ERROR_FILE_NOT_FOUND) {
        _tprintf(TEXT("文件没有找到。\n"));
        exit(1);
    }
        
    _tcscpy(szFileName, FindFileData.cFileName);

    FindClose(hFileFind);
}

/* 获取文件大小 */
void ChkFileSize(PCTSTR szFileName)
{
    WIN32_FILE_ATTRIBUTE_DATA FileInfo;
    ULONGLONG FileSize;

    if (!GetFileAttributesEx(szFileName, GetFileExInfoStandard, &FileInfo)) {
        _tprintf(TEXT("调用GetFileAttributesEx函数获取文件属性出错：\t%lu。\n"), GetLastError());
        exit(1);
    }

    if (FileInfo.nFileSizeHigh==0 && FileInfo.nFileSizeLow==0) {
        _tprintf(TEXT("无法映射空文件。\n"));
        exit(1);
    }

    FileSize = FileInfo.nFileSizeHigh;
    // FileSize <<= sizeof(WORD)*8;
    FileSize <<= 32;
    FileSize += FileInfo.nFileSizeLow;
    _tprintf(TEXT("文件大小：\t%I64u字节。\n"), FileSize);
}

/* 获取搜索文件的设备名 */
void GetDeviceName(PCTSTR szFileName, PTSTR szDeviceName)
{
    HANDLE hFile;
    HANDLE hFileMap;
    PVOID pView;

    hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile==INVALID_HANDLE_VALUE) {
        _tprintf(TEXT("调用CreateFile函数创建文件出错：\t%lu。\n"), GetLastError());
        exit(1);
    }

    hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 1, NULL);

    if (hFileMap==NULL) {
        _tprintf(TEXT("调用CreateFileMapping函数创建文件映射对象出错：\t%lu。\n"), GetLastError());
        CloseHandle(hFile);
        exit(1);
    }

    pView = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1);
    if (!pView) {
        _tprintf(TEXT("调用MapViewOfFile函数创建view出错：\t%lu。\n"), GetLastError());
        CloseHandle(hFileMap);
        CloseHandle(hFile);
        exit(1);
    }

    if (GetMappedFileName(GetCurrentProcess(), pView, szDeviceName, MAX_PATH)==0) {
        _tprintf(TEXT("调用GetMappedFileName函数获取设备名出错：\t%lu。\n"), GetLastError());
        UnmapViewOfFile(pView);
        CloseHandle(hFileMap);
        CloseHandle(hFile);
        exit(1);
    }

    UnmapViewOfFile(pView);
    CloseHandle(hFileMap);
    CloseHandle(hFile);
}

/* 将文件的设备名转化为完整的路径 */
void ConvertFullPath(PCTSTR szDeviceName)
{
    // 设备名"\Device\HarddiskVolume5"
    INT iDeviceNameLen = 0;
    // 存储匹配文件设备名的驱动器根路径"D:\"
    TCHAR szVolPathEach[4] = {'\0'};

    // 获取文件设备名前的驱动器设备名的长度
    GetDeviceNameLen(szDeviceName, &iDeviceNameLen);
    // 获取对应的驱动器根路径
    GetVolPath(szDeviceName, szVolPathEach, iDeviceNameLen);

    // 打印匹配的文件完整路径
    _tprintf(TEXT("文件路径：\t%s%s\n"), szVolPathEach, szDeviceName+iDeviceNameLen+1);
}

/* 获取文件设备名前的驱动器设备名的长度 */
void GetDeviceNameLen(PCTSTR szDeviceName, INT *iDeviceNameLen)
{
    // 驱动器设备名有3个反斜杠，统计反斜杠数量
    INT iSlash = 0;

    // 依据驱动器设备名有3个反斜杠的特征
    // 获取驱动器设备名的长度
    while (iSlash<3) {
        if (*(szDeviceName+(*iDeviceNameLen)++)=='\\') {
            iSlash++;
        }
    }

    // QueryDosDevice返回设备名"\Device\HarddiskVolume5"
    // 不含结束反斜杠
    // 驱动器设备名的长度-1
    (*iDeviceNameLen)--;
}

/* 获取对应的驱动器根路径 */
void GetVolPath(PCTSTR szDeviceName, PTSTR szVolPathEach, INT iDeviceNameLen)
{
    // 存储所有驱动器的根路径的字符串的长度
    DWORD dwVolPathAllLen;
    // 存储所有驱动器的根路径的字符串
    TCHAR *VolPathAll;
    // 存储驱动器的卷名
    TCHAR szVolName[MAX_PATH] = {'\0'};
    // 存储驱动器的设备名
    TCHAR szVolDeviceName[MAX_PATH] = {'\0'};

    // 获取存储所有驱动器的根路径的字符串的长度
    dwVolPathAllLen = GetLogicalDriveStrings(0, NULL);
    // 动态分配内存
    VolPathAll = (TCHAR *)calloc(dwVolPathAllLen, sizeof(TCHAR));

    // 判断GetLogicalDriveStrings函数是否失败
    if (GetLogicalDriveStrings(dwVolPathAllLen, VolPathAll)==0) {
        _tprintf(TEXT("调用GetLogicalDriveStrings函数获取所有卷的根路径出错：\t%lu。\n"), GetLastError());
        free(VolPathAll);
        exit(1);
    }

    // 寻找匹配文件的设备名相对应的驱动器根路径
    for (INT i=0; (i<dwVolPathAllLen) && (*(VolPathAll+i)!='\0'); ) {
        // 将存储所有驱动器的根路径的字符串切分成单个驱动器
        CopyMemory(szVolPathEach, VolPathAll+i, 4);
        
        i+=4;

        // 依据驱动器的根路径，获取驱动器的卷名
        // "D:\"  ——>  "\\?\Volume{95bf0695-6ee2-4a6d-8075-82be10d7ef37}\"
        GetVolumeNameForVolumeMountPoint(szVolPathEach, szVolName, MAX_PATH);

        // 剔除前缀"\\?\"和结束的"\"字符
        // 格式化为"Volume{95bf0695-6ee2-4a6d-8075-82be10d7ef37}"，作为QueryDosDevice函数调用的形参
        *(szVolName+_tcslen(szVolName)*sizeof(TCHAR)-1) = '\0';
        
        // 依据驱动器的卷名，获取驱动器的设备名
        // "Volume{95bf0695-6ee2-4a6d-8075-82be10d7ef37}"  ——>  "\Device\HarddiskVolume5"
        QueryDosDevice(szVolName+4, szVolDeviceName, MAX_PATH);

        if (_tcsnccmp(szDeviceName, szVolDeviceName, iDeviceNameLen)==0) {
            _tprintf(TEXT("文件所在的驱动器的设备名：\t%s\n"), szVolDeviceName);
            *(szVolName+_tcslen(szVolName)*sizeof(TCHAR)-1) = '\\';
            _tprintf(TEXT("文件所在的驱动器的卷名：\t%s\n"), szVolName);
            break;
        }    
    }

    free(VolPathAll);
}